﻿using System;
using System.Reflection;
using Domain.Model.Commons.Atrributes;
using NHibernate.Mapping.ByCode.Conformist;
using NHibernate.Mapping.ByCode;

namespace Domain.Model.Commons.Mapping
{
    /// <summary>
    /// 解析类继承定义
    /// </summary>
    public class InheritMappingHelper
    {
        public Type ResolveMappingType(Type entityType)
        {
            object[] attrTypes = entityType.GetCustomAttributes(false);
            if (null != attrTypes && attrTypes.Length > 0)
            {
                foreach (object entry in attrTypes)
                {
                    SubClass subClass = entry as SubClass;
                    if (null != subClass)
                    {
                        Type mappingType = typeof(SubClassMapping<>);
                        return mappingType.MakeGenericType(entityType);
                    }
                }
            }
            return null;
        }
    }

    public class SubClassMapping<TEntity> : SubclassMapping<TEntity> where TEntity : class
    {
        public SubClassMapping()
        {
            Type entityType = typeof(TEntity);
            SubClass define = entityType.GetCustomAttributes(typeof(SubClass), false)[0] as SubClass;
            define.DiscriminatorValue = String.IsNullOrWhiteSpace(define.DiscriminatorValue) ? typeof(TEntity).Name : define.DiscriminatorValue;
            DiscriminatorValue(define.DiscriminatorValue);

            MappingOthoers(entityType);
        }

        protected void MappingOthoers(Type entityType)
        {
            PropertyInfo[] properties = entityType.GetProperties();
            if (null != properties && properties.Length > 0)
            {
                foreach (PropertyInfo entry in properties)
                {
                    MappingProperty(entry, entry.GetCustomAttributes(false));
                }
            }
        }

        private void MappingProperty(PropertyInfo propertyInfo, object[] attributes)
        {
            if (null == attributes || attributes.Length == 0)
            {
                return;
            }
            foreach (object entry in attributes)
            {
                MapProperty(propertyInfo, entry as Domain.Model.Commons.Atrributes.Property);
                MapManyToOne(propertyInfo, entry as ManyToOne);
                MapOneToMany(propertyInfo, entry as OneToMany);
            }
        }

        private void MapProperty(PropertyInfo info, Domain.Model.Commons.Atrributes.Property attribute)
        {
            if (null == attribute)
            {
                return;
            }
            Property(info.Name, map =>
            {
                map.Column((String.IsNullOrEmpty(attribute.ColumnName) ? info.Name : attribute.ColumnName));
            });
        }

        private void MapManyToOne(PropertyInfo info, ManyToOne attribute)
        {
            if (null == attribute)
            {
                return;
            }
            MethodInfo method = this.GetType().GetMethod("NewManyToOne").MakeGenericMethod(info.PropertyType);
            method.Invoke(this, new object[] { info, attribute });
        }

        public void NewManyToOne<TProperty>(PropertyInfo info, ManyToOne attribute) where TProperty : class
        {
            ManyToOne<TProperty>(info.Name, map =>
            {
                map.Column(attribute.ColumnName);
            });
        }

        private void MapOneToMany(PropertyInfo info, OneToMany attribute)
        {
            if (null == attribute)
            {
                return;
            }

            MethodInfo method = this.GetType().GetMethod("NewSet").MakeGenericMethod(info.PropertyType.DetermineCollectionElementType());
            method.Invoke(this, new object[] { info, attribute });
        }

        public void NewSet<TProperty>(PropertyInfo info, OneToMany attribute) where TProperty : class
        {
            Bag<TProperty>(info.Name, map =>
            {
                map.Key(mapping => { mapping.Column(attribute.MappingColumn); });
                map.Lazy(attribute.Lazy);
                map.Cascade(attribute.CascadeStyle);
            }, action => action.OneToMany());
        }
    }
}
